using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Xml;
using Microsoft.Xna.Framework;
using System.Reflection;
using System.IO;
using Xfinity.Engine;
using System.CodeDom;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using System.Diagnostics;
using Xfinity.Systems;
using System.Xml.Serialization;

namespace Xfinity.Materials
{
    public interface IMaterialDescription
    {

    }
    public class MaterialManager
    {
        class TextureContentManager : ContentManager
        {
            public TextureContentManager(IServiceProvider serviceProvider)
                : base(serviceProvider)
            {

            }
            public TextureContentManager(IServiceProvider serviceProvider, string rootDirectory)
                : base(serviceProvider, rootDirectory)
            {

            }
            public Texture2D Load(string assetName)
            {
                Texture2D item = base.Load<Texture2D>(assetName);
                if (!nameToTextureMap.ContainsKey(assetName))
                {
                    nameToTextureMap.Add(assetName, item);
                }
                if (!textureToNameMap.ContainsKey(item))
                {
                    textureToNameMap.Add(item, assetName);
                }
                return item;
            }
        }
        public MaterialManager(Game game)
        {
            content = new TextureContentManager(game.Services, "Content/Textures");
            if (game.Services.GetService(typeof(MaterialManager)) != null)
            {
                game.Services.RemoveService(typeof(MaterialManager));
            }
            game.Services.AddService(typeof(MaterialManager), this);

        }
        private static Dictionary<string, Texture2D> nameToTextureMap = new Dictionary<string, Texture2D>();
        private static Dictionary<Texture2D, string> textureToNameMap = new Dictionary<Texture2D, string>();
        private TextureContentManager content;
        public Texture2D GetTextureFromName(string name)
        {
            if (nameToTextureMap.ContainsKey(name))
            {
                return nameToTextureMap[name];
            }
            else
            {
                try
                {
                    return content.Load(name);
                }
                catch (FileNotFoundException)
                {
                    return null;
                }
            }
        }
        public string GetNameFromTexture(Texture2D texture)
        {
            if (textureToNameMap.ContainsKey(texture))
            {
                return textureToNameMap[texture];
            }
            else
            {
                return texture.Name;
            }
        }

        internal static Type MaterialDescriptionType;
        internal static Assembly MaterialDescriptionAssembly;

        public Material CreateMaterial(IMaterialDescription description)
        {
            throw new NotImplementedException();
            Material m = new Material(this);
            Type materialType = description.GetType();
            foreach (PropertyInfo descriptionField in materialType.GetProperties())
            {
                //TO DO: convert to texture if property names match
                PropertyInfo materialField = typeof(Material).GetProperty(descriptionField.Name);
                if (materialField.PropertyType == typeof(Texture2D))
                {
                    materialField.SetValue(m, GetTextureFromName((string)descriptionField.GetValue(description, null)), null);
                }
                else
                {
                    materialField.SetValue(m, descriptionField.GetValue(description, null), null);
                }
                
            }

            return m;
        }

        [RunOnStartup]
        public static void CreateMaterialDescriptionType()
        {
            #region It might already be compiled... D:
            if (File.Exists(Xfinity.Resources.Resources.CompiledMaterialDescriptionPath))
            {
                MaterialDescriptionAssembly = Assembly.Load(File.ReadAllBytes(Path.GetFullPath(Xfinity.Resources.Resources.CompiledMaterialDescriptionPath)));
                
                Version existingVersion = MaterialDescriptionAssembly.GetName().Version;
                AssemblyName name = Assembly.GetExecutingAssembly().GetName();
                Version thisVersion = name.Version;
                if (existingVersion == thisVersion)
                {
                    foreach (Type t in MaterialDescriptionAssembly.GetTypes())
                    {
                        if (t.FullName == "Xfinity.Materials.MaterialDescription")
                        {
                            MaterialDescriptionType = t;
                            return;
                        }
                    }
                }                
            }
            #endregion
            #region The namespace
            CodeNamespace ns = new CodeNamespace("Xfinity.Materials");
            ns.Imports.Add(new CodeNamespaceImport("Microsoft.Xna.Framework"));
            ns.Imports.Add(new CodeNamespaceImport("Microsoft.Xna.Framework.Graphics"));
            #endregion
            #region Material Description class
            CodeTypeDeclaration materialDeclaration = new CodeTypeDeclaration("MaterialDescription");

            ns.Types.Add(materialDeclaration);

            materialDeclaration.IsStruct = false;
            materialDeclaration.Attributes = MemberAttributes.Public;
            materialDeclaration.BaseTypes.Add(new CodeTypeReference(typeof(IMaterialDescription)));
            #region Property loop
            foreach (PropertyInfo materialProperty in typeof(Material).GetProperties())
            {
                CodeTypeReference propertyType;
                if (materialProperty.PropertyType.IsSubclassOf(typeof(Texture)) || materialProperty.PropertyType.IsSubclassOf(typeof(Effect)) || materialProperty.PropertyType == typeof(Effect))
                {
                    propertyType = new CodeTypeReference(typeof(string));
                }
                else if (materialProperty.PropertyType == typeof(MaterialManager))
                {
                    continue;
                }
                else
                {
                    propertyType = new CodeTypeReference(materialProperty.PropertyType);
                }


                string fieldName = "backingField____________" + materialProperty.Name + "_____" + materialProperty.PropertyType.Name;
                CodeMemberField field = new CodeMemberField(propertyType, fieldName);

                materialDeclaration.Members.Add(field);

                CodeMemberProperty property = new CodeMemberProperty();
                XmlElementAttribute[] elements = (XmlElementAttribute[])materialProperty.GetCustomAttributes(typeof(XmlElementAttribute), true);
                if (elements.Length >= 1)
                {
                    property.Name = elements[0].ElementName;
                }
                else
                {
                    property.Name = materialProperty.Name;
                }
                property.Type = propertyType;
                property.HasGet = true;
                property.HasSet = true;

                property.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName)));
                property.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName), new CodePropertySetValueReferenceExpression()));
                property.Attributes = MemberAttributes.Public;
                materialDeclaration.Members.Add(property);
            }
            #endregion
            #endregion
            #region Compilation
            CodeCompileUnit compileUnit = new CodeCompileUnit();

            string version = Assembly.GetExecutingAssembly().GetName().Version.ToString();

            CodeAttributeDeclaration assemblyVer = new CodeAttributeDeclaration(new CodeTypeReference(typeof(AssemblyVersionAttribute)), new CodeAttributeArgument(new CodePrimitiveExpression(version)));
            CodeAttributeDeclaration assemblyFileVer = new CodeAttributeDeclaration(new CodeTypeReference(typeof(AssemblyFileVersionAttribute)), new CodeAttributeArgument(new CodePrimitiveExpression(version)));
            compileUnit.AssemblyCustomAttributes.Add(assemblyVer);
            compileUnit.AssemblyCustomAttributes.Add(assemblyFileVer);
            compileUnit.Namespaces.Add(ns);
            compileUnit.ReferencedAssemblies.Add(typeof(Vector3).Assembly.Location);
            compileUnit.ReferencedAssemblies.Add(typeof(IMaterialDescription).Assembly.Location);
            compileUnit.ReferencedAssemblies.Add(typeof(Game).Assembly.Location);
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters paramters = new CompilerParameters();
            paramters.GenerateInMemory = false;
            paramters.CompilerOptions = "/optimize";
            paramters.IncludeDebugInformation = true;
            paramters.OutputAssembly = Xfinity.Resources.Resources.CompiledMaterialDescriptionPath;

            string path = Path.GetDirectoryName(Xfinity.Resources.Resources.CompiledMaterialDescriptionPath);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(Path.GetDirectoryName(Xfinity.Resources.Resources.CompiledMaterialDescriptionPath));
            }

            CompilerResults results = provider.CompileAssemblyFromDom(paramters, compileUnit);
            #endregion
            #region Check for compile errors
            if (!results.Errors.HasErrors)
            {
                MaterialDescriptionAssembly = MaterialDescriptionAssembly = Assembly.Load(File.ReadAllBytes(Path.GetFullPath(Xfinity.Resources.Resources.CompiledMaterialDescriptionPath)));//results.CompiledAssembly;
            }
            else
            {
                throw new InvalidProgramException();
            }
            #endregion
            #region Return the type
            foreach (Type t in MaterialDescriptionAssembly.GetTypes())
            {
                if (t.FullName == "Xfinity.Materials.MaterialDescription")
                {
                    MaterialDescriptionType = t;
                    return;
                }
            }
            #endregion
            #region The type wasn't found; throw
            InvalidProgramException exception = new InvalidProgramException("Compile errors.");
            foreach (CompilerError error in results.Errors)
            {
                exception.Data.Add(error.ErrorNumber, error.ErrorText);
            }
            if (Debugger.IsAttached)
            {
                Debugger.Break();
            }
            throw exception;
            #endregion
        }
    }
}